"""
Copyright (c) 2022 Huawei Technologies Co.,Ltd.

openGauss is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:

          http://license.coscl.org.cn/MulanPSL2

THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
"""
"""
Case Type   : 端口故障类
Case Name   : probackup-restore增量恢复时，备节点基础端口占用(恢复时备节点会被停掉，可测)
Create At   : 2021/07/13
Owner       : @wan005
Description :
    1.初始化备份目录
    gs_probackup init -B {backup_dir}
    2.添加一个新的备份实例
    gs_probackup add-instance -B {backup_dir} -D {cluster/dn1}
    --instance={instance_name}
    3.创建指定实例的备份 并查询backup_id
    gs_probackup backup -B {backup_dir}  --instance={instance_name}
    -b full -d postgres -p {port}
    4.stop备机，起线程给备机注入基础端口被占用故障
    ./cfe "inject rNet_port_conflict (port) values({port})"
    5.起线程从指定实例的备份中恢复数据
    gs_ctl stop -D {dn1}
    gs_probackup restore -B {backup_dir} --instance {instance_name}
    -D {cluster/dn1} -i {backup_id}
    6.清除故障 恢复环境
    ./cfe "clean rNet_port_conflict where port ={port}"
    gs_om -t restart
    gs_om -t status --detail
    7.以backup_dir启动备节点
    gs_ctl start -D {backup_id} -M standby
    8.运行TPCC
    9.检查数据一致性
Expect      :
    1.成功
    2.成功
    3.成功
    4.注入故障成功
    5.恢复数据成功 恢复操作不需要连接数据库 端口没有影响
    6.清除故障 恢复环境
    ./cfe "clean rNet_port_conflict where port ={port}"
    7.指定备份路径启动成功
    8.成功
    9.数据一致
History     :
    Modified by @wan005 2021/8/4 : 优化故障清理断言和teardown
    Modified by @wan005 2022/1/11 : 优化nc安装
"""
import os
import time
import unittest

from testcase.utils.ComThread import ComThread
from testcase.utils.Common import Common
from testcase.utils.CommonSH import CommonSH
from testcase.utils.CommonSH import RestartDbCluster
from testcase.utils.Constant import Constant
from testcase.utils.Logger import Logger
from yat.test import Node, macro


class ReliabilityPort(unittest.TestCase):
    nodes = ("PrimaryDbUser", "Standby1DbUser", "Standby2DbUser")

    @RestartDbCluster(*nodes)
    def setUp(self):
        self.log = Logger()
        self.primary_sh = CommonSH(self.nodes[0])
        self.primary_node = Node(self.nodes[0])

        self.constant = Constant()
        self.com = Common()
        self.primary_root_node = Node(node="PrimaryRoot")
        self.sta1_root_node = Node(node="Standby1Root")
        self.standby1_node = Node(self.nodes[1])
        self.log.info("setUp-实例化:ok")

        for i in range(3):
            result = self.com.get_sh_result(Node(self.nodes[i]),
                                            "nc --version")
            self.log.info(result)
            if not "Version" in result:
                result = self.com.get_sh_result(Node(self.nodes[i]),
                                                "yum -y install nc")
                self.log.info(result)
        self.log.info("setUp-安装nc:ok")

        self.log.info("Opengauss_Reliability_Port_Case006:开始执行")
        self.log.info("创建备份文件夹")
        self.backup_path = os.path.join(os.path.join(
            os.path.dirname(macro.DB_INSTANCE_PATH)),
            "probackup_dir")
        result = self.com.get_sh_result(self.standby1_node,
                                        f"rm -rf {self.backup_path},"
                                        f"mkdir {self.backup_path}")
        self.log.info(result)
        self.assertNotIn("bash", result)
        self.log.info("创建恢复用文件夹")
        self.dn_path = os.path.join(macro.DB_INSTANCE_PATH, "tempdn1")
        result = self.com.get_sh_result(self.standby1_node,
                                        f"rm -rf {self.dn_path},"
                                        f"mkdir {self.dn_path}")
        self.log.info(result)
        self.assertNotIn("bash", result)

        self.port = Node("PrimaryDbUser").db_port
        self.db_name = Node("PrimaryDbUser").db_name
        self.inject_cmd = f"rNet_port_conflict (port) values({self.port})"
        self.clean_cmd = f"rNet_port_conflict where port ={self.port}"

    def test_main(self):
        try:
            self.log.info("步骤1：初始化备份目录")
            result = self.com.get_sh_result(self.standby1_node,
                                            f"source {macro.DB_ENV_PATH};"
                                            f"gs_probackup init "
                                            f"-B {self.backup_path}")
            self.log.info(result)
            self.assertIn("successfully inited", result)
            self.log.info("步骤2：添加一个新的备份实例")
            result = self.com.get_sh_result(self.standby1_node,
                                            f"source {macro.DB_ENV_PATH};"
                                            f"gs_probackup add-instance "
                                            f"-B  {self.backup_path} "
                                            f"-D {macro.DB_INSTANCE_PATH} "
                                            f"--instance=pro1")
            self.log.info(result)
            self.assertIn("'pro1' successfully inited", result)
            self.log.info("步骤3：创建指定实例的备份")
            result = self.com.get_sh_result(self.standby1_node,
                                            f"source {macro.DB_ENV_PATH};"
                                            f"gs_probackup backup "
                                            f"-B {self.backup_path}  "
                                            f"--instance=pro1 -b full "
                                            f"-d {self.db_name} "
                                            f"-p {self.port} --ttl=5")
            self.log.info(result)
            self.assertIn('completed', result)
            self.log.info("查询buckup_id")
            backup_idpath = os.path.join(self.backup_path, "backups/pro1")
            result = self.com.get_sh_result(self.standby1_node,
                                            f"ls {backup_idpath}")
            self.log.info(result)
            backup_id = result.split()[1] \
                if result.split()[0] == "pg_probackup.conf" \
                else result.split()[0]
            self.log.info(backup_id)

            self.log.info("步骤4:stop备机，起线程给备机注入基础端口被占用故障")
            self.log.info("停止备节点")
            result = CommonSH(self.nodes[1]).executeGsctl("stop",
                                                          "server stopped")
            self.assertTrue(result)
            self.log.info("查看当前端口未被占用")
            shell_cmd = f"netstat -anp|grep {self.port}"
            self.log.info(shell_cmd)
            result = self.sta1_root_node.sh(shell_cmd).result()
            self.log.info(result)
            self.assertNotIn("gaussdb", result)

            self.log.info("注入故障")
            session1 = ComThread(self.com.cfe_inject,
                                 args=(self.sta1_root_node,
                                       self.inject_cmd))
            session1.setDaemon(True)
            session1.start()
            time.sleep(2)

            self.log.info("步骤5：起线程从指定实例的备份中恢复数据")
            self.log.info("从指定实例的备份中恢复数据")
            session2 = ComThread(self.com.get_sh_result,
                                 args=(self.standby1_node,
                                       f"source {macro.DB_ENV_PATH};"
                                       f"gs_probackup restore "
                                       f"-B {self.backup_path} "
                                       f"--instance pro1 "
                                       f"-D {self.dn_path} "
                                       f"-i {backup_id}"))
            session2.setDaemon(True)
            session2.start()

            session1.join(120)
            result = session1.get_result()
            self.log.info(result)

            self.log.info("注入故障后查看当前端口被占用")
            result = self.sta1_root_node.sh(shell_cmd).result()
            self.log.info(result)
            self.assertIn(self.port, result)
            self.assertIn("0.0.0.0", result)
            self.assertIn("/nc", result)
            self.assertNotIn("gaussdb", result)

            self.log.info("注入故障后恢复结果")
            session2.join(60*10)
            result = session2.get_result()
            self.log.info(result)
            self.assertIn('completed', result)

        finally:
            self.log.info("步骤6:清除故障 恢复环境")
            result = self.com.cfe_clean(self.sta1_root_node,
                                        self.clean_cmd)
            self.log.info(result)
            self.assertIn("successful execution", result)

            self.log.info("查看当前端口未被占用")
            shell_cmd = f"netstat -anp|grep {self.port}"
            self.log.info(shell_cmd)
            result = self.sta1_root_node.sh(shell_cmd).result()
            self.log.info(result)
            self.assertEqual("", result.strip())

        self.log.info(f"步骤7：指定路径{self.dn_path}启动备机：成功")
        sql = f"source {macro.DB_ENV_PATH};" \
              f"gs_ctl start -D {self.dn_path} -M standby"
        result = self.com.get_sh_result(self.standby1_node, sql)
        self.log.info(result)
        self.assertIn("server started", result)
        sql = f"source {macro.DB_ENV_PATH};" \
              f"gs_ctl stop -D {self.dn_path}"
        result = self.com.get_sh_result(self.standby1_node, sql)
        self.log.info(result)
        self.assertIn("server stopped", result)
        self.log.info("重启数据库 恢复为原有dn1路径")
        result = self.primary_sh.stopDbCluster()
        self.log.info(result)
        result = self.primary_sh.startDbCluster()
        self.log.info(result)
        db_status = self.primary_sh.getDbClusterStatus()
        self.log.info(db_status)
        self.assertTrue("Degraded" in db_status or "Normal" in db_status)

        self.log.info("步骤8:运行TPCC")
        result = self.com.startTPCC(self.primary_root_node, macro.TPCC_PATH)
        self.log.info(result)
        self.assertIn(self.constant.TPCC_SUCCESS_MSG, result)

        self.log.info('检查主备是否同步')
        time.sleep(10)
        self.log.info('获取节点数量')
        node_num = self.primary_sh.get_node_num()
        res = self.primary_sh.check_location_consistency("primary", node_num)
        self.assertTrue(res)

        self.log.info('步骤9:检查数据一致性')
        nodes = (self.primary_node, Node(self.nodes[1]), Node(self.nodes[2]))
        flag = self.com.check_data_sample_by_all(r'\d', *nodes)
        self.assertTrue(flag)
        table_dict = self.com.format_sql_result(
            self.primary_sh.executDbSql(r'\d'))
        table_name = table_dict.get('Name')
        for name in table_name:
            select_sql = f'select count(*) from {name};'
            flag = self.com.check_data_sample_by_all(select_sql,
                                                     *nodes)
            self.assertTrue(flag)

    def tearDown(self):
        self.log.info("环境清理")
        self.com.cfe_clean(self.sta1_root_node, self.clean_cmd)

        sql = f"source {macro.DB_ENV_PATH};" \
              f"gs_ctl stop -D {self.dn_path}"
        result = self.com.get_sh_result(self.standby1_node, sql)
        self.log.info(result)
        result = CommonSH(self.nodes[1]).stopDbCluster()
        self.log.info(result)
        result = CommonSH(self.nodes[1]).startDbCluster()
        self.log.info(result)

        self.com.get_sh_result(self.standby1_node,
                               f"rm -rf {self.backup_path} {self.dn_path}")

        db_status = self.primary_sh.getDbClusterStatus("detail")
        self.log.info(db_status)
        self.assertTrue("Degraded" in db_status or "Normal" in db_status)
        self.log.info("Opengauss_Reliability_Port_Case006:执行完成")
